/*
 * shade.c - This file contains the functions that perform surface shading.
 *
 *  $Id: shade.c,v 1.103 2009/05/18 03:31:48 johns Exp $
 */

#include "machine.h"
#include "types.h"
#include "macros.h"
#include "threads.h"
#include "light.h"
#include "intersect.h"
#include "maths/vector.h"
#include "trace.h"
#include "shade.h"
#include "rayqueue.h"
#include "parallel.h"

#include "measures/measuring.h"

/******************** GLOBAL VARS ********************************************************/
#ifndef NOMEDIDAS
t_measures_global medidas;
#endif
/*****************************************************************************************/

/***************************************************************************************
 * Lowest Quality Shader - Returns the raw color of an object.
 *
 *no se ha modificado porque no tiene recursiones
 ***************************************************************************************/

color lowest_shader_it(ray * incident) {
	int numints;
	object const * obj;
	flt t = FHUGE;
	color col;

	numints = closest_intersection(&t, &obj, incident);
	/* find the number of intersections */
	/* and return the closest one.      */

	if (numints < 1) {
		/* if there weren't any object intersections then return the */
		/* black for the pixel color.                                */
		col.r = 0.0;
		col.g = 0.0;
		col.b = 0.0;

		return col;
	}

	col.r = 1.0;
	col.g = 1.0;
	col.b = 1.0;

	return col;
}

/***************************************************************************************
 * Low Quality Shader - Returns raw texture color of objects hit, nothing else.
 *
 * no tiene recursiones no se modifica
 *
 **************************************************************************************/

color low_shader_it(ray * incident) {
	int numints;
	object const * obj;
	vector hit;
	flt t = FHUGE;

	numints = closest_intersection(&t, &obj, incident);
	/* find the number of intersections */
	/* and return the closest one.      */

	if (numints < 1) {
		/* if there weren't any object intersections then return the */
		/* background texture for the pixel color.                   */
		return incident->scene->bgtexfunc(incident);
	}

	RAYPNT(hit, (*incident), t) /* find the point of intersection from t */
	incident->opticdist = FHUGE;
	return obj->tex->texfunc(&hit, obj->tex, incident);
}

/***************************************************************************************
 * Medium Quality Shader - Includes a subset of the rendering features
 *
 *
 ***************************************************************************************/
// TODO: un error no identificado con los colores

color medium_shader_it(ray * incident) {
	color col, diffuse, phongcol, auxcol;
	shadedata shadevars;
	flt inten;
	flt t = FHUGE;
	object const * obj;
	int numints;
	list * cur;
	int thread = incident->scene->threadid;

	/* initialize values */
	col.r = 0;
	col.g = 0;
	col.b = 0;

/****************** ADD FIRST RAY TO QUEUE *********************************/

	incident->contribution = 1.0;
	rt_rq_add_ray(incident->scene->queue, incident,thread);

/****************** GET RAYS FROM QUEUE ************************************/
	/* while we didn't finished with the rays spawned */
	while ((incident = rt_rq_get_ray(incident->scene->queue,thread))!= NULL) {

		auxcol.r = 0;
		auxcol.g = 0;
		auxcol.b = 0;

/****************** OBJECTS INTERSECTION **************************************/

		numints = closest_intersection(&t, &obj, incident);
		/* find the number of intersections */
		/* and return the closest one.      */

/****************** NO INTERSECTION ****************************************/
		if (numints < 1) { /* si no intersecta con nadie, color de fondo */

			/* if there weren't any object intersections then return the */
			/* background texture for the pixel color.                   */
			auxcol = incident->scene->bgtexfunc(incident);

			/* calculate fog effects */
			if (incident->scene->fog.fog_fctn != NULL) {
				auxcol = fog_color(incident, auxcol, t);
			}

			ColorScale(&auxcol, incident->contribution);
			ColorAccum(&col, &auxcol);

			/* free pointer (store it for later use */
			if (incident->generado)
				rt_rq_eraseray(incident->scene->queue,incident,thread);

			continue; // this ray is evaluated, go next one.
		}

/***************** INTERSECT SOMETHING ***************************************/

		RAYPNT(shadevars.hit, (*incident), t) /* find point of intersection from t */
		incident->opticdist += t;
		obj->methods->normal((const void*)obj,
				             (const void*) &shadevars.hit,
				             (const void*)incident,
				             (void*)&shadevars.N); /* find the surface normal */

		/* execute the object's texture function */
		auxcol = obj->tex->texfunc(&shadevars.hit, obj->tex, incident);

/******************* LIGHT INTERSECTION **************************************/
		/* no lighting modification of the object original color */

		if (obj->tex->flags & RT_TEXTURE_ISLIGHT) {
			/* if the current object is a light, then we  */
			/* will only use the object's base color      */
			ColorScale(&auxcol, incident->contribution);
			ColorAccum(&col, &auxcol);

			/* free pointer (store it for later use */
			if (incident->generado)
				rt_rq_eraseray(incident->scene->queue,incident,thread);

			continue; /* this ray is evaluated, go next one. */
		}

/******************** OTHER OBJECTS INTERSECTION *****************************/

		diffuse.r = 0.0;
		diffuse.g = 0.0;
		diffuse.b = 0.0;
		phongcol = diffuse;

/******************** LIGHTING OF THE COLISION POINT *************************/
		/* medium shader, no projected shadows */

		/* here is where we spawn the rays for the lights */
		if ((obj->tex->diffuse > MINCONTRIB) || (obj->tex->phong > MINCONTRIB)) {
			flt light_scale = incident->scene->light_scale;
			cur = incident->scene->lightlist;
			while (cur != NULL) { /* loop for light contributions */
				light * li = (light *) cur->item; /* set li=to the current light  */
				inten = light_scale * li->shade_diffuse(li, &shadevars);

				/* add in diffuse lighting for this light if we're facing it */
				if (inten > MINCONTRIB) {
					/* calculate diffuse lighting component */
					ColorAddS(&diffuse, &((standard_texture *) li->tex)->col,
							inten);

					/* phong type specular highlights */
					if (obj->tex->phong > MINCONTRIB) {
						flt phongval = light_scale
								* incident->scene->phongfunc(incident,
										&shadevars, obj->tex->phongexp);
						if (obj->tex->phongtype == RT_PHONG_METAL)
							ColorAddS(&phongcol, &col, phongval
									* obj->tex->phong);
						else
							ColorAddS(&phongcol,
									&((standard_texture *) li->tex)->col,
									phongval * obj->tex->phong);
					}
				}

				cur = cur->next;
			}
		}


		if (obj->tex->outline > 0.0) {
			apiflt outlinefactor;
			apiflt edgefactor = VDot(&shadevars.N, &incident->d);
			edgefactor *= edgefactor;
			edgefactor = 1.0 - edgefactor;
			edgefactor = 1.0 - pow(edgefactor, (1.0 - obj->tex->outlinewidth)
					* 32.0);
			outlinefactor = (1.0 - obj->tex->outline) + (edgefactor
					* obj->tex->outline);
			ColorScale(&diffuse, obj->tex->diffuse * outlinefactor);
		} else {
			ColorScale(&diffuse, obj->tex->diffuse);
		}

		auxcol.r *= (diffuse.r + obj->tex->ambient); /* do a product of the     */
		auxcol.g *= (diffuse.g + obj->tex->ambient); /* diffuse intensity with  */
		auxcol.b *= (diffuse.b + obj->tex->ambient); /* object color + ambient  */

		if (obj->tex->phong > MINCONTRIB) {
			ColorAccum(&col, &phongcol);
		}

/******************** SPAWN AND ADD TO THE QUEUE REFLECTED RAYS ***********************/

		/* spawn reflection rays if necessary */
		/* note: this will overwrite the old intersection list */
		if (obj->tex->specular > MINCONTRIB) {
			shade_reflection_it(incident,
										&shadevars,
										incident->contribution * obj->tex->specular);
		}

/******************** SPAWN AND ADD TO THE QUEUE TRANSMITED RAYS ***********************/

		/* spawn transmission rays / refraction */
		/* note: this will overwrite the old intersection list */
		if (obj->tex->opacity < (1.0 - MINCONTRIB)) {
			shade_transmission_it(incident,
										&shadevars,
						          incident->contribution * (1.0 - obj->tex->opacity));
		}

/*********************** FOG  ***********************************************************/
		/* calculate fog effects */
		if (incident->scene->fog.fog_fctn != NULL) {
			auxcol = fog_color(incident, auxcol, t);
		}

		/* free pointer (store it for later use) */
		if (incident->generado)
			rt_rq_eraseray(incident->scene->queue,incident,thread);


		ColorScale(&auxcol, incident->contribution);
		ColorAccum(&col,&auxcol);

#ifndef NOMEDIDAS
		/* one ray finished, acount it */
		medidas_add_ray(thread);
#endif

	}/* while more spawned rays*/
#ifndef NOMEDIDAS
	medidas_end_pixel(thread);
#endif

	return col; /* return the color of the shaded pixel... */
}

/*****************************************************************************************
 * Full Quality Shader - Includes all possible rendering features
 *
 *
 *****************************************************************************************/

color full_shader_it(ray * incident) {
	color col, diffuse, ambocccol, phongcol, auxcol;
	shadedata shadevars;
	ray shadowray;      /* esto es difetente */
	flt inten;
	flt t = FHUGE;
	object const * obj;
	int numints;
	list * cur;
	int thread = incident->scene->threadid;

	/* initialize values */
	col.r = 0;
	col.g = 0;
	col.b = 0;

	/* este es el shader que se usa por defecto */

/****************** ADD FIRST RAY TO QUEUE *********************************/

	incident->contribution = 1.0;  /* el primer rayo tiene contribución máxima al color del pixel */
	rt_rq_add_ray(incident->scene->queue, incident,thread);

/****************** GET RAYS FROM QUEUE ************************************/
	/* while we didn't finished with the rays spawned */
	while ((incident = rt_rq_get_ray(incident->scene->queue,thread))!= NULL) {

		auxcol.r = 0;
		auxcol.g = 0;
		auxcol.b = 0;

/****************** OBJECTS INTERSECTION **************************************/

		numints = closest_intersection(&t, &obj, incident);
		/* find the number of intersections */
		/* and return the closest one.      */

/****************** NO INTERSECTION ****************************************/
		if (numints < 1) {

			/* if there weren't any object intersections then return the */
			/* background texture for the pixel color.                   */
			auxcol = incident->scene->bgtexfunc(incident);

			/* calculate fog effects */
			if (incident->scene->fog.fog_fctn != NULL) {
				auxcol = fog_color(incident, auxcol, t);
			}

			ColorScale(&auxcol, incident->contribution);
			ColorAccum(&col, &auxcol);

			/* free pointer (store it for later use */
			if (incident->generado)
				rt_rq_eraseray(incident->scene->queue,incident,thread);

			continue; // this ray is evaluated, go next one.
		}

		/******************Intersect some object, get its color*****************************/

		RAYPNT(shadevars.hit, (*incident), t) /* find point of intersection from t */
		incident->opticdist += t;
		obj->methods->normal(obj,
		                     &shadevars.hit,
		                     incident,
		                     &shadevars.N); /* find the surface normal */

		/* execute the object's texture function */
		auxcol = obj->tex->texfunc(&shadevars.hit, obj->tex, incident);

/******************* LIGHT INTERSECTION **************************************/


		if (obj->tex->flags & RT_TEXTURE_ISLIGHT) {
			/* if the current object is a light, then we  */

			ColorScale(&auxcol, incident->contribution);
			ColorAccum(&col, &auxcol);

			/* free pointer (store it for later use */
			if (incident->generado)
				rt_rq_eraseray(incident->scene->queue,incident,thread);

			continue; /* this ray is evaluated, go next one. */
		}

		/******************  LIGHT ON OBJECT  *****************************************/
		diffuse.r = 0.0;
		diffuse.g = 0.0;
		diffuse.b = 0.0;
		ambocccol = diffuse;
		phongcol = diffuse;

/******************** LIGHTING OF THE COLISION POINT *************************/


	/* we spawn a ray for each light */
		if ((obj->tex->diffuse > MINCONTRIB) || (obj->tex->phong > MINCONTRIB)) {
			flt light_scale = incident->scene->light_scale;
			cur = incident->scene->lightlist;

			/** shade clipping: */
			/* http://en.wikipedia.org/wiki/Clipping_(computer_graphics) */
			if (incident->scene->flags & RT_SHADE_CLIPPING) {
				shadowray.add_intersection = add_clipped_shadow_intersection;
			} else {
				shadowray.add_intersection = add_shadow_intersection;
			}
			shadowray.serial = incident->serial + 1; /* track ray serial number */
			shadowray.mbox = incident->mbox;
			shadowray.scene = incident->scene;

			while (cur != NULL) { /* loop for light contributions */
				light * li = (light *) cur->item; /* set li=to the current light  */
				inten = light_scale * li->shade_diffuse(li, &shadevars);

				/* add in diffuse lighting for this light if we're facing it */
				if (inten > MINCONTRIB) {
					/* test for a shadow */
					shadowray.o = shadevars.hit;
					shadowray.d = shadevars.L;
					shadowray.maxdist = shadevars.Llen;
					shadowray.flags = RT_RAY_SHADOW;
					shadowray.serial++;
					intersect_objects(&shadowray); /* trace the shadow ray */

					if (!shadow_intersection(&shadowray)) {
						/* If the light isn't occluded, then we modulate it by any */
						/* transparent surfaces the shadow ray encountered, and    */
						/* proceed with illumination calculations                  */
						inten *= shadowray.intstruct.shadowfilter;

						/* calculate diffuse lighting component */
						ColorAddS(&diffuse, &((standard_texture *) li->tex)->col,
								inten);

						/* phong type specular highlights */
						if (obj->tex->phong > MINCONTRIB) {
							flt phongval = light_scale
									* incident->scene->phongfunc(incident,
											&shadevars, obj->tex->phongexp);
							if (obj->tex->phongtype == RT_PHONG_METAL)
								ColorAddS(&phongcol, &auxcol, phongval
										* obj->tex->phong);
							else
								ColorAddS(&phongcol,
										&((standard_texture *) li->tex)->col,
										phongval * obj->tex->phong);
						}
					}
				}

				cur = cur->next;
			}
			incident->serial = shadowray.serial; /* track ray serial number */

			/* add ambient occlusion lighting, if enabled */
			if (incident->scene->ambocc.numsamples > 0) {
				ambocccol = shade_ambient_occlusion(incident, &shadevars);
			}
		}

		/* accumulate diffuse and ambient occlusion together */
		diffuse.r += ambocccol.r;
		diffuse.g += ambocccol.g;
		diffuse.b += ambocccol.b;

		if (obj->tex->outline > 0.0) {
			apiflt outlinefactor;
			apiflt edgefactor = VDot(&shadevars.N, &incident->d);
			edgefactor *= edgefactor;
			edgefactor = 1.0 - edgefactor;
			edgefactor = 1.0 - pow(edgefactor, (1.0 - obj->tex->outlinewidth)
					* 32.0);
			outlinefactor = (1.0 - obj->tex->outline) + (edgefactor
					* obj->tex->outline);
			ColorScale(&diffuse, obj->tex->diffuse * outlinefactor);
		} else {
			ColorScale(&diffuse, obj->tex->diffuse);
		}

		/* do a product of the diffuse intensity with object color + ambient */
		auxcol.r *= (diffuse.r + obj->tex->ambient);
		auxcol.g *= (diffuse.g + obj->tex->ambient);
		auxcol.b *= (diffuse.b + obj->tex->ambient);

		if (obj->tex->phong > MINCONTRIB) {
			ColorAccum(&auxcol, &phongcol);
		}

		/********************* LLAMADA RECURSIVA RAYOS REBOTADOS *********************/
		/* spawn reflection rays if necessary */
		/* note: this will overwrite the old intersection list */
		if (obj->tex->specular > MINCONTRIB) {

			shade_reflection_it(incident,
					            &shadevars,
					            incident->contribution * obj->tex->specular);

		}

		/********************* LLAMADA RECURSIVA RAYOS QUE VAN ATRAVES***************/
		/* spawn transmission rays / refraction */
		/* note: this will overwrite the old intersection list */
		if (obj->tex->opacity < (1.0 - MINCONTRIB)) {
			shade_transmission_it(incident,
								  &shadevars,
								  incident->contribution * (1.0 - obj->tex->opacity));
		}

		/* calculate fog effects */
		if (incident->scene->fog.fog_fctn != NULL) {
			auxcol = fog_color(incident, auxcol, t);
		}

		/* free pointer (store it for later use) */
		if (incident->generado)
			rt_rq_eraseray(incident->scene->queue,incident,thread);

		ColorScale(&auxcol, incident->contribution);
		ColorAccum(&col,&auxcol);

#ifndef NOMEDIDAS
		/* one ray finished, acount it */
		medidas_add_ray(thread);
#endif
	}
#ifndef NOMEDIDAS
	medidas_end_pixel(thread);
#endif

	return col; /* return the color of the shaded pixel... */
}

void shade_reflection_it (ray * incident,
						  const shadedata * shadevars,
		                  flt  spec) {
	ray *specray;
	vector R;

	/* Do recursion depth test immediately to early-exit ASAP */
	if (incident->depth <= 1) {
		/* if ray is truncated, return the background texture as its color */
		return; /*incident->scene->bgtexfunc(incident);*/
	}

	specray = rt_rq_newray((incident->scene)->queue,incident->scene->threadid);
	specray->generado = 1;

	specray->depth = incident->depth - 1; /* go up a level in recursion depth */

	VAddS(-2.0 * (incident->d.x * shadevars->N.x + incident->d.y *
			      shadevars->N.y + incident->d.z * shadevars->N.z), &shadevars->N,
			      &incident->d, &R);

	specray->o = shadevars->hit;
	specray->d = R; /* reflect incident ray about normal */
	specray->o = Raypnt(specray, EPSILON); /* avoid numerical precision bugs */
	specray->maxdist = FHUGE; /* take any intersection */
	specray->opticdist = incident->opticdist;
	specray->add_intersection = add_regular_intersection; /* ray type  */
	specray->flags = RT_RAY_REGULAR; /* infinite ray, to start with */
	specray->serial = incident->serial + 1; /* next serial number */
	specray->mbox = incident->mbox;
	specray->scene = incident->scene; /* global scenedef info */
	specray->randval = incident->randval; /* random number seed */
	specray->frng = incident->frng; /* 32-bit FP RNG handle */

	/* inlined code from trace() to eliminate one level of recursion */
	intersect_objects(specray); /* trace specular reflection ray */

	/* no more recursive calls */
	/*col = specray.scene->shader(&specray);  */

	/* specify contribution for color scale */
	specray->contribution = spec;

	/* push it in the queue */
	rt_rq_add_ray(specray->scene->queue,specray,incident->scene->threadid);

	incident->serial = specray->serial; /* update the serial number */
	incident->frng = specray->frng; /* update AO RNG state      */

	return;
}

void shade_transmission_it(ray * incident, const shadedata * shadevars, flt trans) {
	ray *transray;


	/* Do recursion depth test immediately to early-exit ASAP */
	if (incident->depth <= 1) {
		/* if ray is truncated, return the background texture as its color */
		return; /*incident->scene->bgtexfunc(incident);*/
	}
	transray = rt_rq_newray((incident->scene)->queue,incident->scene->threadid);
	transray->generado = 1;

	transray->depth = incident->depth - 1; /* go up a level in recursion depth */

	transray->o = shadevars->hit;
	transray->d = incident->d; /* ray continues on incident path */
	transray->o = Raypnt(transray, EPSILON); /* avoid numerical precision bugs */
	transray->maxdist = FHUGE; /* take any intersection */
	transray->opticdist = incident->opticdist;
	transray->add_intersection = add_regular_intersection; /* ray type  */
	transray->depth = incident->depth - 1; /* go up a level in recursion depth */
	transray->flags = RT_RAY_REGULAR; /* infinite ray, to start with */
	transray->serial = incident->serial + 1; /* update serial number */
	transray->mbox = incident->mbox;
	transray->scene = incident->scene; /* global scenedef info */
	transray->randval = incident->randval; /* random number seed */
	transray->frng = incident->frng; /* 32-bit FP RNG handle */

	/* inlined code from trace() to eliminate one level of recursion */
	intersect_objects(transray); /* trace transmission ray */
	/* iterative version
	 * col = transray.scene->shader(&transray); */

	incident->serial = transray->serial; /* update the serial number */
	incident->frng = transray->frng; /* update AO RNG state      */

	transray->contribution = trans;

	/* push it in the queue */
	rt_rq_add_ray(transray->scene->queue,transray,incident->scene->threadid);


	return;

}

